package com.samphanie.dries.service;

import com.samphanie.dries.domain.Auth;
import com.samphanie.dries.domain.Role;
import com.samphanie.dries.domain.User;
import com.samphanie.dries.exception.ApiException;
import com.samphanie.dries.repository.RoleRepo;
import com.samphanie.dries.repository.UserRepo;
import com.samphanie.dries.util.JwtUtil;
import com.samphanie.dries.util.TotpUtil;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import static com.samphanie.dries.util.Constants.ROLE_USER;

/**
 * @Description:
 * @Author ZSY
 * @createTime 2021/3/2 20:32
 */
@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepo userRepo;
    private final RoleRepo roleRepo;
    private final PasswordEncoder passwordEncoder;
    private final JwtUtil jwtUtil;
    private final TotpUtil totpUtil;

    /**
     * 注册一个新用户
     *
     * @param user 用户实体
     * @return 保存后的对象
     */
    @Transactional
    public User register(User user) {

        return roleRepo.findOptionalByAuthority(ROLE_USER)
                .map(role -> {
                    Set<Role> roles = new HashSet<>();
                    roles.add(role);
                    val userToSave = user
                            .withAuthorities(roles)
                            .withPassword(passwordEncoder.encode(user.getPassword()))
                            .withMfaKey(totpUtil.encodeKeyToString())
                            ;
                    return userRepo.save(userToSave);
                })
                .orElseThrow(() -> new ApiException("注册失败"));
    }

    public Auth login(String username, String password) throws AuthenticationException {
        return userRepo.findOptionalByUsername(username)
                .filter(user -> passwordEncoder.matches(password, user.getPassword()))
                .map(user -> new Auth(
                        jwtUtil.createAccessToken(user),
                        jwtUtil.createRefreshToken(user)
                ))
                .orElseThrow(() -> new BadCredentialsException("用户名或密码错误"))
                ;
    }

    public Auth login(UserDetails userDetails) {
        return new Auth(jwtUtil.createAccessToken(userDetails), jwtUtil.createRefreshToken(userDetails));
    }

    public void upgradePasswordEncodingIfNeeded(User user, String rawPassword) {
        if (passwordEncoder.upgradeEncoding(user.getPassword())) {
            userRepo.save(user.withPassword(passwordEncoder.encode(rawPassword)));
        }
    }

    public User saveUser(User user) {
        return userRepo.save(user);
    }

    public Auth loginWithTotp(User user) {
        val toSave = user.withMfaKey(totpUtil.encodeKeyToString());
        val saved = saveUser(toSave);
        return login(saved);
    }

    public Optional<String> createTotp(User user) {
        return totpUtil.createTotp(user.getMfaKey());
    }

    /**
     * 取得全部用户列表
     *
     * @return 全部用户列表
     */
    public List<User> findAll() {
        return userRepo.findAll();
    }

    public UserDetails updatePassword(User user, String newPassword) {
        return userRepo.findOptionalByUsername(user.getUsername())
                .map(u -> (UserDetails) userRepo.save(u.withPassword(newPassword)))
                .orElse(user);
    }

    public boolean isUsernameExisted(String username) {
        return userRepo.countByUsername(username) > 0;
    }

    public boolean isEmailExisted(String email) {
        return userRepo.countByEmail(email) > 0;
    }

    public boolean isMobileExisted(String mobile) {
        return userRepo.countByMobile(mobile) > 0;
    }

    public Optional<User> findOptionalByUsername(String username) {
        return userRepo.findOptionalByUsername(username);
    }

    public Optional<User> findOptionalByUsernameAndPassword(String username, String password) {
        return userRepo.findOptionalByUsername(username)
                .filter(user -> passwordEncoder.matches(password, user.getPassword()));
    }

    /**
     * 在编辑用户的场景下，判断电子邮件是否重复，需要规避用户本身的 email
     *
     * @param email    电邮地址
     * @param username 用户名
     * @return 存在与否
     */
    public boolean isEmailExistedAndUsernameIsNot(String email, String username) {
        return userRepo.countByEmailAndUsernameIsNot(email, username) > 0;
    }

    /**
     * 在编辑用户的场景下，判断电子邮件是否重复，需要规避用户本身的手机号
     *
     * @param mobile   手机号
     * @param username 用户名
     * @return 存在与否
     */
    public boolean isMobileExistedAndUsernameIsNot(String mobile, String username) {
        return userRepo.countByMobileAndUsernameIsNot(mobile, username) > 0;
    }
}
